﻿using System;
using System.Collections.Generic;

namespace Stauffware.Common.Core
{
	/// <summary>
	/// Encapsulates an immutable value that has one of two states:
	/// in its "normal" state, it can serve as value of type T;
	/// in its "error" state, it contains an (unthrown) Exception.
	/// </summary>
	/// <typeparam name="T">
	/// The "normal" value type.  It must not be an Exception.
	/// </typeparam>

	public struct ErrorOr<T>
	{
		/// <summary>
		/// Gets whether this ErrorOr instance is an error (contains an Exception).
		/// </summary>
		public bool IsError { get { return this._exception != null; } }

		/// <summary>
		/// Gets the normal value of this ErrorOr instance.  
		/// If this instance is an error, then its Exception is thrown.
		/// </summary>
		public T Value
		{
			get
			{
				if (this.IsError)
					throw this._exception;
				else return this._value;
			}
		}

		/// <summary>
		/// Gets the Exception for this ErrorOr instance.  The instance must be
		/// an error (not a normal value), or an assertion will fail.
		/// </summary>
		public Exception Exception
		{
			get
			{
				Require.Condition(this.IsError, "this ErrorOr<T> instance is not an error");
				return this._exception;
			}
		}

		/// <summary>
		/// Constructs an ErrorOr in the "normal" state.
		/// </summary>
		/// <param name="x">the value of the ErrorOr</param>
		public ErrorOr (T x)
		{
			Require.Condition(!typeof(Exception).IsAssignableFrom(typeof(T)),
							"generic type must not be an Exception");
			this._value = x;
			this._exception = null;
		}

		/// <summary>
		/// Constructs an ErrorOr in the "error" state.
		/// </summary>
		/// <param name="exc">
		/// An Exception that describes the error condition.  If the exception has 
		/// already been thrown, its stack trace is copied to Data["OldStackTrace"].
		/// </param>
		public ErrorOr (Exception exc)
		{
			Require.Condition(!typeof(Exception).IsAssignableFrom(typeof(T)),
							"generic type must not be an Exception");
			Require.NotNull(exc);
			// If the exception has already been thrown, make a copy of its stack
			// trace so that it isn't totally lost if the exception is re-thrown.
			if (exc.StackTrace != null)
				exc.Data["OldStackTrace"] = exc.StackTrace;
			this._exception = exc;
			this._value = default(T);
		}

		/// <inherit/>
		public override string ToString ( )
		{
			if (this._exception != null)
				return this._exception.Message;
			if (this._value == null)
				return "<null>";
			return ErrorUtils.ValueToString(this._value);
		}

		/// <summary>
		/// Implicitly casts an ErrorOr&lt;T&gt; to a T.  
		/// If the ErrorOr is an error, its Exception is thrown.
		/// </summary>
		public static implicit operator T (ErrorOr<T> e) { return e.Value; }

		/// <summary>
		/// Implicitly casts an ErrorOr&lt;T&gt; to an Exception.
		/// If the ErrorOr is not an error, an assertion fails.
		/// </summary>
		public static implicit operator Exception (ErrorOr<T> e) { return e.Exception; }

		/// <summary>
		/// Implicitly casts a T to an ErrorOr&lt;T&gt;.
		/// </summary>
		public static implicit operator ErrorOr<T> (T value) { return new ErrorOr<T>(value); }

		/// <summary>
		/// Implicitly casts an Exception to an ErrorOr&lt;T&gt;.
		/// </summary>
		public static implicit operator ErrorOr<T> (Exception x) { return new ErrorOr<T>(x); }

		private readonly T _value;				// the normal value
		private readonly Exception _exception;	// the error value
	}
}
